﻿#include "Effect.h"
#include "../Forms/Client.CMain.h"
#include "../MirScenes/GameScene.h"
#include "../../Shared/Functions/Functions.h"
#include "../../Shared/Globals.h"
#include "PlayerObject.h"
#include "../MirSounds/SoundManager.h"

using namespace Client::MirGraphics;
using namespace Client::MirScenes;
using namespace Client::MirSounds;

namespace Client::MirObjects
{

    Effect::Effect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, long long starttime, bool drawBehind)
    {
        Library = library;
        BaseIndex = baseIndex;
        Count = count == 0 ? 1 : count;
        Duration = duration;
        Start = starttime == 0 ? CMain::Time : starttime;

        NextFrame = Start + (Duration / Count) * (CurrentFrame + 1);
        Owner = owner;
        Source = Owner->CurrentLocation;

        DrawBehind = drawBehind;
    }

    Effect::Effect(MLibrary *library, int baseIndex, int count, int duration, Point *source, long long starttime, bool drawBehind)
    {
        Library = library;
        BaseIndex = baseIndex;
        Count = count == 0 ? 1 : count;
        Duration = duration;
        Start = starttime == 0 ? CMain::Time : starttime;

        NextFrame = Start + (Duration / Count) * (CurrentFrame + 1);
        Source = source;

        DrawBehind = drawBehind;
    }

    void Effect::SetStart(long long start)
    {
        Start = start;

        NextFrame = Start + (Duration / Count) * (CurrentFrame + 1);
    }

    void Effect::Process()
    {
        if (CurrentFrame == 1)
        {
            if (Played != nullptr)
            {
                for (auto listener : Played->listeners())
                {
                    listener(this, EventArgs.Empty);
                }
            }
        }
        if (CMain::Time <= NextFrame)
        {
            return;
        }

        if (Owner != nullptr && Owner->SkipFrames)
        {
            CurrentFrame++;
        }

        if (++CurrentFrame >= Count)
        {
            if (Repeat && (RepeatUntil == 0 || CMain::Time < RepeatUntil))
            {
                CurrentFrame = 0;
                Start = CMain::Time + Delay;
                NextFrame = Start + (Duration / Count) * (CurrentFrame + 1);
            }
            else
            {
                Remove();
            }
        }
        else
        {
            NextFrame = Start + (Duration / Count) * (CurrentFrame + 1);
        }

        GameScene::Scene->MapControl->TextureValid = false;


    }

    void Effect::Remove()
    {
        if (Owner != nullptr)
        {
            Owner->Effects.Remove(this);
        }
        else
        {
            MapControl::Effects.Remove(this);
        }

        if (Complete != nullptr)
        {
            for (auto listener : Complete->listeners())
            {
                listener(this, EventArgs.Empty);
            }
        }
    }

    void Effect::Draw()
    {
        if (CMain::Time < Start)
        {
            return;
        }

        if (Owner != nullptr)
        {
            DrawLocation = Owner->DrawLocation;
        }
        else
        {
            DrawLocation = new Point((Source->X - MapObject::User->Movement->X + MapControl::OffSetX) * MapControl::CellWidth, (Source->Y - MapObject::User->Movement->Y + MapControl::OffSetY) * MapControl::CellHeight);
            DrawLocation->Offset(MapObject::User->OffSetMove);
        }


        if (Blend)
        {
            Library->DrawBlend(BaseIndex + CurrentFrame, DrawLocation, Color::White, true, Rate);
        }
        else
        {
            Library->Draw(BaseIndex + CurrentFrame, DrawLocation, Color::White, true);
        }
    }

    void Effect::Clear()
    {
        Complete->addListener("nullptr", nullptr);
        Played->addListener("nullptr", nullptr);
    }

std::vector<Missile*> Missile::Missiles;

    Missile::Missile(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, Point *target, bool direction16) : Effect(library, baseIndex, count, duration, owner)
    {
        Missiles.push_back(this);
        Source = Owner->CurrentLocation;
        Destination = target;
        Direction = direction16 ? MapControl::Direction16(Source, Destination) : static_cast<int>(Functions::DirectionFromPoint(Source, Destination));
    }

    Missile::Missile(MLibrary *library, int baseIndex, int count, int duration, Point *source, Point *target) : Effect(library, baseIndex, count, duration, source)
    {
        Missiles.push_back(this);
        Destination = target;

        Direction = MapControl::Direction16(Source, Destination);


    }

    void Missile::Process()
    {
        if (CMain::Time < Start)
        {
            return;
        }

        if (Target != nullptr)
        {
            Destination = Target->CurrentLocation;
        }
        else if (!Explode)
        {
            int dist = Functions::MaxDistance(Owner->CurrentLocation, Destination);

            if (dist < Globals::DataRange)
            {
                Destination->Offset(Destination->X - Source->X, Destination->Y - Source->Y);
            }
        }

        Duration = Functions::MaxDistance(Source, Destination) * 50;
        Count = Duration / Interval;
        if (Count == 0)
        {
            Count = 1;
        }

        Effect::Process();
    }

    void Missile::Remove()
    {
        Effect::Remove();
        Missiles.Remove(this);
    }

    void Missile::Draw()
    {
        if (CMain::Time < Start)
        {
            return;
        }


        int index = BaseIndex + (CurrentFrame % FrameCount) + Direction * (Skip + FrameCount);

        DrawLocation = new Point((Source->X - MapObject::User->Movement->X + MapControl::OffSetX) * MapControl::CellWidth, (Source->Y - MapObject::User->Movement->Y + MapControl::OffSetY) * MapControl::CellHeight);
        DrawLocation->Offset(MapObject::User->OffSetMove);

        int x = (Destination->X - Source->X) * MapControl::CellWidth;
        int y = (Destination->Y - Source->Y) * MapControl::CellHeight;


        DrawLocation->Offset(x * CurrentFrame / Count, y * CurrentFrame / Count);

        if (!Blend)
        {
            Library->Draw(index, DrawLocation, Color::White, true);
        }
        else
        {
            Library->DrawBlend(index, DrawLocation, Color::White, true, Rate);
        }
    }

std::vector<InterruptionEffect*> InterruptionEffect::effectlist;

    InterruptionEffect::InterruptionEffect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, bool blend, long long starttime) : Effect(library, baseIndex, count, duration, owner)
    {
        Repeat = true;
        Blend = blend;
        effectlist.push_back(this);
    }

    void InterruptionEffect::Process()
    {
        if (!noProcess)
        {
            Effect::Process();
        }
    }

    void InterruptionEffect::Remove()
    {
        Effect::Remove();
        effectlist.Remove(this);
    }

    void InterruptionEffect::Draw()
    {
        if (!(static_cast<PlayerObject*>(Owner))->Concentrating)
        {
            Remove();
        }
        else if ((static_cast<PlayerObject*>(Owner))->ConcentrateInterrupted)
        {
            noProcess = true;
        }
        else
        {
            noProcess = false;
        }
        if (!noProcess)
        {
            Effect::Draw();
        }
    }

    int InterruptionEffect::GetOwnerEffectID(unsigned int objectID)
    {
        for (int i = 0; i < effectlist.size(); i++)
        {
            if (effectlist[i]->Owner->ObjectID != objectID)
            {
                continue;
            }
            return i;
        }
        return -1;
    }

    ElementsEffect::ElementsEffect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, bool blend, int elementType, int killtime, bool loopon) : Effect(library, baseIndex, count, duration, owner)
    {
        Repeat = true;
        Blend = blend;
        myType = elementType;
        killAt = killtime;
        //
        loopit = loopon;
        StopSounds();
        StartSound();
    }

    void ElementsEffect::Process()
    {

        Effect::Process();
    }

    void ElementsEffect::StartSound()
    {
        SoundManager::PlaySound(20000 + 126 * 10 + 4 + myType, loopit);
    }

    void ElementsEffect::StopSounds()
    {
        for (int i = 0; i <= 3; i++)
        {
            SoundManager::StopSound(20000 + 126 * 10 + 5 + i);
        }
    }

    void ElementsEffect::Remove()
    {
        SoundManager::StopSound(20000 + 126 * 10 + 4 + myType);
        Effect::Remove();
    }

    void ElementsEffect::Draw()
    {
        if (!(static_cast<PlayerObject*>(Owner))->HasElements)
        {
            Remove();
        }
        if ((static_cast<PlayerObject*>(Owner))->ElementsLevel >= killAt && myType < 4)
        {
            Remove();
        }
        Effect::Draw();
    }

std::vector<DelayedExplosionEffect*> DelayedExplosionEffect::effectlist;

    DelayedExplosionEffect::DelayedExplosionEffect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, bool blend, int Stage, long long until) : Effect(library, baseIndex, count, duration, owner)
    {
        Repeat = (Stage == 2) ? false : true;
        Blend = blend;
        stage = Stage;
        effectlist.push_back(this);
        if (stage == 1)
        {
            SoundManager::PlaySound(20000 + 125 * 10);
        }
        if (stage == 2)
        {
            SoundManager::PlaySound(20000 + 125 * 10 + 5);
        }
    }

    void DelayedExplosionEffect::Process()
    {
        Effect::Process();
    }

    void DelayedExplosionEffect::Remove()
    {
        Effect::Remove();
        effectlist.Remove(this);
    }

    void DelayedExplosionEffect::Draw()
    {
        if (Owner->Dead)
        {
            Remove();
            return;
        }
        Effect::Draw();
    }

    int DelayedExplosionEffect::GetOwnerEffectID(unsigned int objectID)
    {
        for (int i = 0; i < effectlist.size(); i++)
        {
            if (effectlist[i]->Owner->ObjectID != objectID)
            {
                continue;
            }
            return i;
        }
        return -1;
    }

    SpecialEffect::SpecialEffect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, bool blend, bool drawBehind, unsigned int type) : Effect(library, baseIndex, count, duration, owner, 0, drawBehind)
    {
        Blend = blend;
        DrawBehind = drawBehind;
        EffectType = type;
        Light = -1;
    }

    void SpecialEffect::Process()
    {
        Effect::Process();
    }

    BuffEffect::BuffEffect(MLibrary *library, int baseIndex, int count, int duration, MapObject *owner, bool blend, BuffType buffType) : Effect(library, baseIndex, count, duration, owner, 0)
    {
        Repeat = true;
        Blend = blend;
        BuffType = buffType;
        Light = -1;
    }

    void BuffEffect::Process()
    {
        Effect::Process();
    }

std::vector<TrackableEffect*> TrackableEffect::effectlist;

    TrackableEffect::TrackableEffect(Effect *baseEffect, const std::string &effName) : Effect(baseEffect->Library, baseEffect->BaseIndex, baseEffect->Count, baseEffect->Duration, baseEffect->Owner, baseEffect->Start)
    {
        Repeat = baseEffect->Repeat;
        RepeatUntil = baseEffect->RepeatUntil;
        Blend = baseEffect->Blend;

        EffectName = effName;

        effectlist.push_back(this);
    }

    int TrackableEffect::GetOwnerEffectID(unsigned int objectID, const std::string &effectName)
    {
        for (int i = 0; i < effectlist.size(); i++)
        {
            if (effectlist[i]->Owner->ObjectID != objectID)
            {
                continue;
            }
            if (effectName != "null" && effectlist[i]->EffectName != effectName)
            {
                continue;
            }
            return i;
        }
        return -1;
    }

    void TrackableEffect::Process()
    {
        Effect::Process();

        if (Owner == nullptr)
        {
            Remove();
        }
        else if (Owner->Dead)
        {
            Remove();
        }
    }

    void TrackableEffect::Remove()
    {
        Effect::Remove();
        effectlist.Remove(this);
    }

    void TrackableEffect::RemoveNoComplete()
    {
        if (Owner != nullptr)
        {
            Owner->Effects.Remove(this);
        }
        else
        {
            MapControl::Effects.Remove(this);
        }
    }

    LightEffect::LightEffect(int duration, MapObject *owner, long long starttime, int lightDistance, std::optional<Color*> &lightColour) : Effect(nullptr, 0, 0, duration, owner, starttime)
    {
        Light = lightDistance;
        LightColour = !lightColour ? Color::White : static_cast<Color*>(lightColour);
    }

    LightEffect::LightEffect(int duration, Point *source, long long starttime, int lightDistance, std::optional<Color*> &lightColour) : Effect(nullptr, 0, 0, duration, source, starttime)
    {
        Light = lightDistance;
        LightColour = !lightColour ? Color::White : static_cast<Color*>(lightColour);
    }

    void LightEffect::Process()
    {
        if (CMain::Time >= Start + Duration)
        {
            Remove();
        }
        GameScene::Scene->MapControl->TextureValid = false;

    }

    void LightEffect::Draw()
    {

        if (CMain::Time < Start)
        {
            return;
        }


        if (Owner != nullptr)
        {
            DrawLocation = Owner->DrawLocation;
        }
        else
        {
            DrawLocation = new Point((Source->X - MapObject::User->Movement->X + MapControl::OffSetX) * MapControl::CellWidth, (Source->Y - MapObject::User->Movement->Y + MapControl::OffSetY) * MapControl::CellHeight);
            DrawLocation->Offset(MapObject::User->OffSetMove);
        }
    }
}
